/*
----------------------------------------------------------------------------------------------------
This source file is part of the Gamut Framework
Copyright (c) 2007 by Ryan Holcombe

This program is free software; you can redistribute it and/or modify it under the terms of the GNU
Lesser General Public License as published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
----------------------------------------------------------------------------------------------------
*/


#include "GamutXMLParser.h"
#include "GamutException.h"
#include "GamutUtils.h"

#define TIXML_USE_STL
#include <xmldump.h>
#include <tinyxml.h>

namespace Gamut
{
	// -------------------------------------------------------------------------------------------//
	XMLParser::XMLParser()
	{
		mDoc = NULL;
		mCurrentPos.pos = NULL;
	}

	// -------------------------------------------------------------------------------------------//
	XMLParser::XMLParser(const std::string& file)
	{
		mDoc = new TiXmlDocument();
		mCurrentPos.pos = NULL;
		
		load(file);
	}

	// -------------------------------------------------------------------------------------------//
	XMLParser::~XMLParser()
	{
		if (mDoc)
			delete mDoc;
		mDoc = NULL;
	}

	// -------------------------------------------------------------------------------------------//
	void XMLParser::load(const std::string& file)
	{
		mDoc = new TiXmlDocument();

		// read in the xml file
		if (!mDoc->LoadFile(file.c_str()))
			throw Exception("XMLParser::loadXML", "Unable to load XML file [" + file + "]");
		
		// set the current position to the first element in the file
		_setElementValues(&mCurrentPos, mDoc->FirstChildElement());
	}
	
	// -------------------------------------------------------------------------------------------//
	bool XMLParser::stepInto()
	{
		if (mCurrentPos.pos == NULL)
			throw Exception("XMLParser::stepInto", "The position pointer is not pointing to an element");
		
		if (mCurrentPos.pos->FirstChildElement() == NULL)
			return (0);
		
		mCurrentPos.pos = mCurrentPos.pos->FirstChildElement();
		_setElementValues(&mCurrentPos);
		return (1);
	}
	
	// -------------------------------------------------------------------------------------------//
	bool XMLParser::stepBack()
	{
		if (mCurrentPos.pos == NULL)
			throw Exception("XMLParser::stepBack", "The position pointer is not pointing to an element");
		
		if (mCurrentPos.pos->Parent() == NULL)
			return (0);

		mCurrentPos.pos = static_cast<TiXmlElement*>(mCurrentPos.pos->Parent());
		_setElementValues(&mCurrentPos);
		return (1);
	}
		
	// -------------------------------------------------------------------------------------------//
	bool XMLParser::next(const std::string& title)
	{
		if (mCurrentPos.pos == NULL)
			throw Exception("XMLParser::next", "The position pointer is not pointing to an element");
		
		if (title.size() > 0)
		{
			if (mCurrentPos.pos->NextSiblingElement(title) == NULL)
				return (0);
		
			_setElementValues(&mCurrentPos, mCurrentPos.pos->NextSiblingElement(title));
		}
		else
		{
			if (mCurrentPos.pos->NextSiblingElement() == NULL)
				return (0);
		
			_setElementValues(&mCurrentPos, mCurrentPos.pos->NextSiblingElement());
		}
		return (1);
	}
	
	// -------------------------------------------------------------------------------------------//
	bool XMLParser::previous()
	{
		if (mCurrentPos.pos == NULL)
			throw Exception("XMLParser::previous", "The position pointer is not pointing to an element");
		
		if (mCurrentPos.pos->PreviousSibling() == NULL)
			return (0);
		
		mCurrentPos.pos = static_cast<TiXmlElement*>(mCurrentPos.pos->PreviousSibling());
		_setElementValues(&mCurrentPos);
		return (1);
	}
	
	// -------------------------------------------------------------------------------------------//
	bool XMLParser::findSibling(const std::string& title)
	{
		if (mCurrentPos.pos == NULL)
			throw Exception("XMLParser::next", "The position pointer is not pointing to an element");

		// go to the beginning of the current level
		mCurrentPos.pos = static_cast<TiXmlElement*>(mCurrentPos.pos->Parent());
		
		
		// is there an element with the specified title?
		if (mCurrentPos.pos->FirstChildElement(title) == NULL)
			return (0);
		
		_setElementValues(&mCurrentPos, mCurrentPos.pos->FirstChildElement(title));
		return (1);
	}
	
	// -------------------------------------------------------------------------------------------//
	bool XMLParser::findChild(const std::string& title)
	{
		if (mCurrentPos.pos == NULL)
			throw Exception("XMLParser::next", "The position pointer is not pointing to an element");
		
		if (mCurrentPos.pos->FirstChildElement(title) == NULL)
			return (0);
		
		_setElementValues(&mCurrentPos, mCurrentPos.pos->FirstChildElement(title));
		return (1);
	}
	
	// -------------------------------------------------------------------------------------------//
	void XMLParser::root()
	{
		_setElementValues(&mCurrentPos, mDoc->FirstChildElement());
	}
	
	// -------------------------------------------------------------------------------------------//
	const std::string& XMLParser::getTitle()
	{
		return (mCurrentPos.title);
	}
	
	// -------------------------------------------------------------------------------------------//
	const std::string& XMLParser::getName()
	{
		return (mCurrentPos.name);
	}
	
	// -------------------------------------------------------------------------------------------//
	const std::string& XMLParser::getType()
	{
		return (mCurrentPos.type);
	}
	
	// -------------------------------------------------------------------------------------------//
	boost::any XMLParser::getValue()
	{
		return (mCurrentPos.value);
	}
	
	// -------------------------------------------------------------------------------------------//
	float XMLParser::getValueAsFloat()
	{
		if (mCurrentPos.value.empty())
			throw Exception("XMLParser::getValueAsFloat", "Value is empty");
		
		float retVal;
		try {
			retVal = boost::any_cast<float>(mCurrentPos.value);
		}
		catch (const boost::bad_any_cast &b) {
			throw Exception("XMLParser::getValueAsFloat",
							"boost:any_cast failure, " + mCurrentPos.title +
							" could not be cast as this type");
		}
		
		return retVal;
	}
	
	// -------------------------------------------------------------------------------------------//
	int XMLParser::getValueAsInt()
	{
		if (mCurrentPos.value.empty())
			throw Exception("XMLParser::getValueAsFloat", "Value is empty");
		
		int retVal;
		try {
			retVal = boost::any_cast<int>(mCurrentPos.value);
		}
		catch (const boost::bad_any_cast &b) {
			throw Exception("XMLParser::getValueAsInt",
							"boost:any_cast failure, " + mCurrentPos.title +
							" could not be cast as this type");
		}
		
		return retVal;
	}
	
	// -------------------------------------------------------------------------------------------//
	const std::string XMLParser::getValueAsString()
	{
		if (mCurrentPos.value.empty())
			throw Exception("XMLParser::getValueAsFloat", "Value is empty");
		
		std::string retVal;
		try {
			retVal = boost::any_cast<std::string>(mCurrentPos.value);
		}
		catch (const boost::bad_any_cast &b) {
			throw Exception("XMLParser::getValueAsString",
							"boost:any_cast failure, " + mCurrentPos.title +
							" could not be cast as this type");
		}
		
		return retVal;
	}
	
	// -------------------------------------------------------------------------------------------//
	vector3 XMLParser::getValueAsVector3()
	{
		if (mCurrentPos.value.empty())
			throw Exception("XMLParser::getValueAsFloat", "Value is empty");
		
		vector3 retVal;
		try {
			retVal = boost::any_cast<vector3>(mCurrentPos.value);
		}
		catch (const boost::bad_any_cast &b) {
			throw Exception("XMLParser::getValueAsVector3",
							"boost:any_cast failure, " + mCurrentPos.title +
							" could not be cast as this type");
		}
		
		return retVal;
	}
	
	
	
	
	
	
	
	
	// -------------------------------------------------------------------------------------------//
	void XMLParser::_setElementValues(XMLElement *element, TiXmlElement *tixml)
	{
		const char *title, *name, *type, *value;
		
		if (tixml != NULL)
			 element->pos = tixml;
		
		// clear the variables
		element->title = "";
		element->name = "";
		element->value = boost::any();
		element->type = "";
		
		if (element->pos == NULL)
			throw Exception("XMLParser::_setElementValues",
							"There was no position in the tinyxml file passed to the function");
		
		title = element->pos->Value();
		name = element->pos->Attribute("name");
		type = element->pos->Attribute("type");
		value = element->pos->Attribute("value");
		
		if (title)
			element->title = title;
		if (name)
			element->name = name;
		if (type)
			element->type = type;
		if (value)
			_setValue(element, value, type);
	}
	
	// -------------------------------------------------------------------------------------------//
	void XMLParser::_setValue(XMLElement *element, const std::string& value, const std::string& type)
	{
		// get a boost::any value from the value and type
		if (type == "string")
			element->value = value;
		else if (type == "int")
			element->value = Gamut::argConvert<int>(value);
		else if (type == "float")
			element->value = Gamut::argConvert<float>(value);
		else if (type == "vector3")
		{
			std::string::size_type delim1 = std::string::npos;
			std::string::size_type delim2 = std::string::npos;
			
			// find out how the data is split (either spaces or commas)
			if (value.find(' ') != std::string::npos)
			{
				delim1 = value.find(' ');
				delim2 = value.rfind(' ');
			}
			else if (value.find(',') != std::string::npos)
			{
				delim1 = value.find(',');
				delim2 = value.rfind(',');
			}
			
			// make sure the delimiters were set correctly
			if (delim1 == std::string::npos || delim2 == std::string::npos)
				throw Exception("XMLParser::_setValue",
								"Invalid format for vector3 (must be seperated by spaces or commas");
			
			vector3 v3Value;
			v3Value.x = Gamut::argConvert<float>(value.substr(0, delim1));
			v3Value.y = Gamut::argConvert<float>(value.substr(delim1+1, delim2-delim1));
			v3Value.z = Gamut::argConvert<float>(value.substr(delim2+1));
			element->value = v3Value;
		}
		else
		{
			throw Exception("XMLParser::_setValue", "Unable to set value, type [" + type + "] is unknown");
		}
	}

	// -------------------------------------------------------------------------------------------//
	void XMLParser::_outputToStdout()
	{
		if (mDoc)
			dump_to_stdout(mDoc);
		else
			throw Gamut::Exception("XMLParser::_outputToStdout", "No XML Document has been loaded");
	}

}
